package com.lau.videoeffect.multiplayer

import android.content.Context
import android.graphics.SurfaceTexture
import android.media.MediaPlayer
import android.net.Uri
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.opengl.Matrix
import android.view.Surface
import com.lau.videoeffect.utils.Program
import java.lang.Exception
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

class VideoRenderer : IVideoRenderer, MediaPlayer.OnVideoSizeChangedListener, SurfaceTexture.OnFrameAvailableListener {

    companion object {

        private const val TAG = "VideoRenderer"

        const val VERTEX_SHADER =
            "attribute vec4 aPos;\n" +
                    "attribute vec4 aTexCoord;\n" +
                    "varying vec2 vTexCoord;\n" +
                    "uniform mat4 uMatrix;\n" +
                    "uniform mat4 uSTMatrix;\n" +
                    "void main() {\n" +
                    "    vTexCoord = (uSTMatrix * aTexCoord).xy;\n" +
                    "    gl_Position = uMatrix * aPos;\n" +
                    "}"

        const val FRAGMENT_SHADER =
            "#extension GL_OES_EGL_image_external : require\n" +
                    "precision mediump float;\n" +
                    "varying vec2 vTexCoord;\n" +
                    "uniform samplerExternalOES sTexture;\n" +
                    "void main() {\n" +
                    "    gl_FragColor=texture2D(sTexture, vTexCoord);\n" +
                    "}"
    }

    private val vertexData = floatArrayOf(
        1f, -1f, 0f,
        -1f, -1f, 0f,
        1f, 1f, 0f,
        -1f, 1f, 0f
    )

    private val textureVertexData = floatArrayOf(
        1f, 0f,
        0f, 0f,
        1f, 1f,
        0f, 1f
    )

    private val mMediaPlayer: MediaPlayer
    private var mSurfaceTexture: SurfaceTexture? = null
    private var mProgram: Program? = null
    private var mTextureId = 0

    private var aPosLoc = 0
    private var aTextureCoordLoc = 0
    private var uMatrixLoc = 0
    private var uSTMatrixLoc = 0

    private var uTextureSamplerLoc = 0

    private val mSTMatrix = FloatArray(16)
    private var vertexBuffer: FloatBuffer
    private var textureVertexBuffer: FloatBuffer
    private val projectionMatrix = FloatArray(16)

    private var mPlayerPrepared = false
    private var screenWidth = 0
    private var screenHeight = 0

    constructor(context: Context, path: String) {
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(vertexData)
        vertexBuffer.position(0)

        textureVertexBuffer = ByteBuffer.allocateDirect(textureVertexData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(textureVertexData)
        textureVertexBuffer.position(0)

        mMediaPlayer = MediaPlayer()
        mMediaPlayer.setDataSource(context, Uri.parse(path))
        mMediaPlayer.isLooping = true
        mMediaPlayer.setOnVideoSizeChangedListener(this)
    }

    override fun onResume() {
        if (mPlayerPrepared) {
            mMediaPlayer?.start()
        }
    }

    override fun onPause() {
        mMediaPlayer?.pause()
    }

    override fun onRelease() {
        mMediaPlayer?.stop()
        mMediaPlayer?.release()
    }

    override fun onSurfaceChanged(width: Int, height: Int) {
        screenWidth = width
        screenHeight = height

        try {
            mMediaPlayer.prepare()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        mMediaPlayer.start()
        mPlayerPrepared = true
    }

    override fun onSurfaceCreated() {
        mProgram = Program(VERTEX_SHADER, FRAGMENT_SHADER)
        mProgram?.let {
            aPosLoc = it.getAttribLocation("aPos")
            uMatrixLoc = it.getUniformLocation("uMatrix")
            uSTMatrixLoc = it.getUniformLocation("uSTMatrix")
            uTextureSamplerLoc = it.getUniformLocation("sTexture")
            aTextureCoordLoc = it.getAttribLocation("aTexCoord")
        }
        val textures = IntArray(1)
        GLES20.glGenTextures(1, textures, 0)

        mTextureId = textures[0]
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST.toFloat())
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST.toFloat())
        mSurfaceTexture = SurfaceTexture(mTextureId)
        mSurfaceTexture!!.setOnFrameAvailableListener(this)

        val surface = Surface(mSurfaceTexture)
        mMediaPlayer.setSurface(surface)
        surface.release()
    }

    override fun onVideoSizeChanged(p0: MediaPlayer?, p1: Int, p2: Int) {
        updateProjection(p1, p2)
    }

    override fun onFrameAvailable(p0: SurfaceTexture?) {

    }

    override fun onDraw() {
        mSurfaceTexture?.getTransformMatrix(mSTMatrix)

        mSurfaceTexture?.updateTexImage()
        mProgram?.use()
        GLES20.glUniformMatrix4fv(uMatrixLoc, 1, false, projectionMatrix, 0)
        GLES20.glUniformMatrix4fv(uSTMatrixLoc, 1, false, mSTMatrix, 0)

        vertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aPosLoc)
        GLES20.glVertexAttribPointer(aPosLoc, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer)

        textureVertexBuffer.position(0)
        GLES20.glEnableVertexAttribArray(aTextureCoordLoc)
        GLES20.glVertexAttribPointer(aTextureCoordLoc, 2, GLES20.GL_FLOAT, false, 8, textureVertexBuffer)

        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId)

        GLES20.glUniform1i(uTextureSamplerLoc, 0)
        GLES20.glViewport(0, 0, screenWidth, screenHeight)
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
    }

    private fun updateProjection(videoWidth: Int, videoHeight: Int) {
        val screenRatio = screenWidth.toFloat() / screenHeight.toFloat()
        val videoRatio = videoWidth.toFloat() / videoHeight.toFloat()
        if (videoRatio > screenRatio) {
            Matrix.orthoM(projectionMatrix, 0, -1f, 1f, -videoRatio / screenRatio, videoRatio / screenRatio, -1f, 1f)
        } else {
            Matrix.orthoM(projectionMatrix, 0, -screenRatio / videoRatio, screenRatio / videoRatio, -1f, 1f, -1f, 1f)
        }
    }
}